/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * ns-2 simple.tcl script (ported from ns-2)
 * Originally authored by Steve McCanne, 12/19/1996
 */

// Port of ns-2/tcl/ex/simple.tcl to ns-3
//
// Network topology
//
//  h0                                 h0-n0, h1-n0, n1-h2, n2-h2: 2Mb/s, 10ms
//     \    ---- n1 -----              n0-n1, n0-n2:               2Mb/s, 50ms
//      \  /             |
//       n0              n3 --- h2
//      /  \             |
//     /    ---- n2 ----
//  h1
//
// - all links are point-to-point links with indicated one-way BW/delay
// - FTP/TCP flow from n0 to n3, starting at time 1.2 to time 1.35 sec.
// - DropTail queues 
// - Tracing of queues and packet receptions to file "simple-global-routing.tr"

#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
#include "reflex-trace.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("SimpleGlobalRoutingExample");

std::ofstream asciiFlow;
std::ofstream asciiMetric;

void FlowLog( uint32_t time, Time duration)
{
  asciiFlow << time << " " << duration << '\n';
}

void MetricTrace( double oldVal, double newVal )
{
  asciiMetric << Simulator::Now() << " " << newVal << '\n';
}
 

void LogTime()
{
  NS_LOG_INFO( "Time is " << Simulator::Now() );
  Simulator::Schedule(Seconds(5), MakeEvent( &LogTime));
}


int 
main (int argc, char *argv[])
{
  // Users may find it convenient to turn on explicit debugging
  // for selected modules; the below lines suggest how to do this
#if 1
  LogComponentEnable ("SimpleGlobalRoutingExample", LOG_LEVEL_INFO);
  //LogComponentEnable("GlobalRoutingHelper", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4GlobalRouting", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4ReactRouting", LOG_LEVEL_ALL);
  //LogComponentEnable("FlowApplication", LOG_LEVEL_ALL);
//  LogComponentEnable("NscTcpSocketImpl", LOG_LEVEL_ALL);
#endif

  // Here, we will explicitly create four nodes.  In more sophisticated
  // topologies, we could configure a node factory.
  NS_LOG_INFO ("Create nodes.Modified");
  NodeContainer h;
  h.Create (3);
  NodeContainer n;
  n.Create (4);

  NodeContainer h0n0 = NodeContainer (h.Get(0), n.Get (0));
  NodeContainer h1n0 = NodeContainer (h.Get(1), n.Get (0));
  NodeContainer h2n3 = NodeContainer (h.Get(2), n.Get (3));

  NodeContainer n0n1 = NodeContainer (n.Get(0), n.Get (1));
  NodeContainer n0n2 = NodeContainer (n.Get(0), n.Get (2));
  NodeContainer n1n3 = NodeContainer (n.Get(1), n.Get (3));
  NodeContainer n2n3 = NodeContainer (n.Get(2), n.Get (3));


  InternetStackHelper reactStack;
  Ipv4ReactRoutingHelper reactRoutingHelper;
  Ipv4GlobalRoutingHelper ipv4GlobalRoutingHelper;
  Ipv4ListRoutingHelper listRouting;
  listRouting.Add (reactRoutingHelper, 1);
  listRouting.Add (ipv4GlobalRoutingHelper, -1);
  reactStack.SetRoutingHelper (listRouting);    
  reactStack.Install (n.Get(0));


  InternetStackHelper internet;
  internet.Install (n.Get(1));
  internet.Install (n.Get(2));
  internet.Install (n.Get(3));

  std::ostringstream oss;
  internet.SetTcp ("ns3::NscTcpL4Protocol","Library",StringValue("liblinux2.6.26.so"));
  //NS_LOG_INFO(oss.str());
  
  internet.Install (h);
  Config::Set ("/NodeList/0/$ns3::Ns3NscStack<linux2.6.26>/net.ipv4.tcp_ecn", StringValue ("3"));
  Config::Set ("/NodeList/1/$ns3::Ns3NscStack<linux2.6.26>/net.ipv4.tcp_ecn", StringValue ("3"));
  Config::Set ("/NodeList/2/$ns3::Ns3NscStack<linux2.6.26>/net.ipv4.tcp_ecn", StringValue ("3"));

  // We create the channels first without any IP addressing information
  NS_LOG_INFO ("Create channels.");
  PointToPointHelper p2p;

  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("2.5ms"));

  NetDeviceContainer hd0 = p2p.Install (h0n0);
  NetDeviceContainer hd1 = p2p.Install (h1n0);
  NetDeviceContainer hd2 = p2p.Install (h2n3);

  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("10ms"));
  NetDeviceContainer d0d1 = p2p.Install (n0n1);
  NetDeviceContainer d0d2 = p2p.Install (n0n2);
  p2p.SetDeviceAttribute ("DataRate", StringValue ("15Mbps"));
  NetDeviceContainer d1d3 = p2p.Install (n1n3);
  p2p.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
  NetDeviceContainer d2d3 = p2p.Install (n2n3);
  NodeContainer bottlenecks = NodeContainer (n.Get(1), n.Get(2));


  NodeContainer::Iterator node;
  unsigned int num;
  std::list<Ptr<PppReflexTraceQueue> > queueTraces;
  for ( node = bottlenecks.Begin(), num=0; node != bottlenecks.End(); node++, num++ )
  {
    for ( unsigned int i = 0; i < (*node)->GetNDevices(); i++ )
    {
      Ptr<PppReflexTraceQueue> test = CreateObject<PppReflexTraceQueue> (); 

      std::ostringstream out, queue;
      out << "mynode"<<(*node)->GetId()<<"-"<<i<<".tr"; 
      test->SetOutput(out.str());

      queue << "/NodeList/" << (*node)->GetId() << "/DeviceList/" << i
                                          << "/$ns3::PointToPointNetDevice/TxQueue";
      test->SetQueue(queue.str());
      queueTraces.push_front(test);
    }
  }
                         
  
  // Later, we add IP addresses.  
  NS_LOG_INFO ("Assign IP Addresses.");
  Ipv4AddressHelper ipv4;
  ipv4.SetBase ("172.0.0.0", "255.255.255.0");
  Ipv4InterfaceContainer hi0 = ipv4.Assign (hd0);
  ipv4.SetBase ("172.0.1.0", "255.255.255.0");
  Ipv4InterfaceContainer hi1 = ipv4.Assign (hd1);
  ipv4.SetBase ("192.0.0.0", "255.255.255.0");
  Ipv4InterfaceContainer hi2 = ipv4.Assign (hd2);

  ipv4.SetBase ("10.0.1.0", "255.255.255.0");
  Ipv4InterfaceContainer i0i1 = ipv4.Assign (d0d1);
  ipv4.SetBase ("10.0.2.0", "255.255.255.0");
  Ipv4InterfaceContainer i0i2 = ipv4.Assign (d0d2);
  ipv4.SetBase ("10.3.1.0", "255.255.255.0");
  Ipv4InterfaceContainer i1i3 = ipv4.Assign (d1d3);
  ipv4.SetBase ("10.3.2.0", "255.255.255.0");
  Ipv4InterfaceContainer i2i3 = ipv4.Assign (d2d3);

  // Create router nodes, initialize routing database and set up the routing
  // tables in the nodes.
  ipv4GlobalRoutingHelper.PopulateRoutingTables ();

  // Create react routes
  Ptr<Ipv4> ipv40 = n.Get(0)->GetObject<Ipv4> ();
  Ptr<Ipv4ReactRouting> staticRouting0 = reactRoutingHelper.GetReactRouting (ipv40);

  // The ifIndex for this outbound route is 1; the first p2p link added
  Ptr<ReflexStat> reflex = staticRouting0->AddDefaultNetworkRouteTo (Ipv4Address ("192.0.0.0"), Ipv4Mask ("255.255.255.0"), 4);
  staticRouting0->AddNetworkRouteTo (Ipv4Address ("192.0.0.0"), Ipv4Mask ("255.255.255.0"), 3);

  /////////////////////////////
  //reflex->TraceConnectWithoutContext("Metric", MakeCallback(&MetricTrace));
 
  // Create the OnOff application to send UDP datagrams of size
  // 210 bytes at a rate of 448 Kb/s
  NS_LOG_INFO ("Create Applications.");

  //uint16_t port = 9;   // Discard port (RFC 863)
  uint16_t port = 8080;


  NS_LOG_INFO ("Create sink.");
  FlowAppHelper sink (Address (InetSocketAddress (hi0.GetAddress (0), port)));
  sink.SetAttribute ("FlowNum", UintegerValue (0));
  sink.SetAttribute ("Local", AddressValue (InetSocketAddress (Ipv4Address::GetAny (), port)));
  ApplicationContainer apps2 = sink.Install (h.Get (2));
  apps2.Start (Seconds (0.0));
  apps2.Stop (Seconds (10));
 

  asciiFlow.open("flowlog.tr");
  asciiMetric.open("metriclog.tr");
  NS_LOG_INFO ("Create flow.");

  FlowAppHelper onoff (Address (InetSocketAddress (hi2.GetAddress (0), port)));
  onoff.SetAttribute ("FlowNum", UintegerValue (5));
  //average flow size of 2MB
  //onoff.SetAttribute ("FlowSize", RandomVariableValue (WeibullVariable (2*1000*1000, 0.5)));
  onoff.SetAttribute ("FlowSize", RandomVariableValue (ConstantVariable (100000)));
  onoff.SetAttribute ("StatCallback", CallbackValue(MakeCallback( &FlowLog)));
  NodeContainer sources = NodeContainer (h.Get(0), h.Get(1));
  //NodeContainer sources = NodeContainer (h.Get(0));
  ApplicationContainer apps = onoff.Install (sources);
  apps.Start (Seconds (0.0));
  apps.Stop (Seconds (10));

  /////////////////
  p2p.EnablePcapAll("reflex-simple");
  NS_LOG_INFO ("Run Simulation.");
  LogTime();
  Simulator::Stop (Seconds (10));
  Simulator::Run ();
  NS_LOG_INFO ("Done.");

  Simulator::Destroy ();
  return 0;
}
